package com.csust.cg.mongodb.service.impl;

import com.csust.cg.mongodb.common.FilterConstant;
import com.csust.cg.mongodb.domain.HttpStatus;
import com.csust.cg.mongodb.domain.Result;
import com.csust.cg.mongodb.domain.vo.DocumentPageVO;
import com.csust.cg.mongodb.domain.vo.QueryVO;
import com.csust.cg.mongodb.exception.ServiceException;
import com.csust.cg.mongodb.service.DocumentsService;
import com.csust.cg.mongodb.utils.MongoUtils;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.InsertOneResult;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.StreamSupport;

import static com.mongodb.client.model.Sorts.ascending;
import static com.mongodb.client.model.Sorts.descending;

/**
 * @author chenguang
 */
@Service
public class DocumentsServiceImpl implements DocumentsService {
    @Override
    public Result getDocumentsInfoList(QueryVO query, String connectionId) {
        MongoClient client = MongoUtils.getClient(connectionId);
        MongoCollection<Document> collection = client.getDatabase(query.getDatabaseName()).getCollection(query.getCollectionName());

        // 返回结果构造
        DocumentPageVO result = new DocumentPageVO();

        AggregateIterable<Document> aggregateIterable = null;

        FindIterable<Document> findIterable = null;

        // 查询文档总数
        long documentCount;

        // 连接集合
        if (query.getCollections() != null && query.getCollections().size() > 0) {
            List<Bson> aggregates = new ArrayList<>();
            for (Map<String, String> collectionMap : query.getCollections()) {
                String name = collectionMap.get("name");
                String localField = collectionMap.get("localField");
                String foreignField = collectionMap.get("foreignField");
                String as = collectionMap.get("as");
                if (StringUtils.isBlank(name) || StringUtils.isBlank(localField)
                        || StringUtils.isBlank(foreignField) || StringUtils.isBlank(as)) {
                    MongoUtils.closeClient(client);
                    return Result.error("连接集合信息不能为空！", HttpStatus.BAD_REQUEST);
                }
                aggregates.add(Aggregates.lookup(name, localField, foreignField, as));
                // 将没有关联到的结果过滤掉
                aggregates.add(Aggregates.match(Filters.ne(as, new ArrayList<String>(0))));
            }
            try {
                aggregateIterable = collection.aggregate(aggregates);
                documentCount = StreamSupport.stream(aggregateIterable.spliterator(), false).count();
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return Result.error("参数错误！", HttpStatus.BAD_REQUEST);
            }
        } else {
            findIterable = collection.find();
            // 查询条件
            if (query.getFilters() != null && query.getFilters().size() > 0) {
                List<Bson> andList = new ArrayList<>();
                List<Bson> orList = new ArrayList<>();
                for (Map<String, String> filter : query.getFilters()) {
                    // 连接条件
                    String name = filter.get("join");
                    if (StringUtils.isBlank(name)) {
                        MongoUtils.closeClient(client);
                        return Result.error("连接条件不能为空！", HttpStatus.BAD_REQUEST);
                    }
                    FilterConstant join = FilterConstant.fromFilterName(name);
                    try {
                        switch (Objects.requireNonNull(join)) {
                            case OR:
                                orList.add(getFilter(filter));
                                break;
                            case AND:
                                andList.add(getFilter(filter));
                                break;
                            default:
                                break;
                        }
                    } catch (ServiceException e) {
                        return Result.error(e.getMessage(), e.getCode());
                    }
                }
                Bson filter;
                if (orList.size() > 0) {
                    filter = Filters.or(Filters.or(orList), Filters.and(andList));
                } else {
                    filter = Filters.and(andList);
                }
                findIterable = findIterable.filter(filter);
            }

            try {
                documentCount = StreamSupport.stream(findIterable.spliterator(), false).count();
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return Result.error("参数错误！", HttpStatus.BAD_REQUEST);
            }

            // 映射条件
            if (query.getProjects() != null && query.getProjects().size() > 0) {
                List<String> includes = new ArrayList<>();
                List<String> excludes = new ArrayList<>();
                for (Map<String, String> projectMap : query.getProjects()) {
                    String value = projectMap.get("value");
                    if (StringUtils.isBlank(value)) {
                        MongoUtils.closeClient(client);
                        return Result.error("映射类型不能为空！", HttpStatus.BAD_REQUEST);
                    }
                    String key = projectMap.get("key");
                    if (StringUtils.isBlank(value)) {
                        MongoUtils.closeClient(client);
                        return Result.error("映射列不能为空！", HttpStatus.BAD_REQUEST);
                    }
                    if ("1".equals(value)) {
                        // 包含
                        includes.add(key);
                    } else {
                        // 不包含
                        excludes.add(key);
                    }
                }
                if (includes.size() > 0) {
                    findIterable = findIterable.projection(Projections.include(includes));
                }
                if (excludes.size() > 0) {
                    findIterable = findIterable.projection(Projections.exclude(excludes));
                }
            }

            // 排序条件
            if (query.getSorts() != null && query.getSorts().size() > 0) {
                List<String> ascends = new ArrayList<>();
                List<String> descends = new ArrayList<>();
                for (Map<String, String> sortMap : query.getSorts()) {
                    String value = sortMap.get("value");
                    if (StringUtils.isBlank(value)) {
                        MongoUtils.closeClient(client);
                        return Result.error("排序类型不能为空！", HttpStatus.BAD_REQUEST);
                    }
                    String key = sortMap.get("key");
                    if (StringUtils.isBlank(value)) {
                        MongoUtils.closeClient(client);
                        return Result.error("排序列不能为空！", HttpStatus.BAD_REQUEST);
                    }
                    if ("1".equals(value)) {
                        // 升序
                        ascends.add(key);
                    } else {
                        // 降序
                        descends.add(key);
                    }
                }
                if (ascends.size() > 0) {
                    findIterable = findIterable.sort(ascending(ascends));
                }
                if (descends.size() > 0) {
                    findIterable = findIterable.sort(descending(descends));
                }
            }

            // 跳过个数
            if (query.getSkip() != null) {
                findIterable = findIterable.skip(query.getSkip() + query.getPageSize() * (query.getCurrentPage() - 1));
            }

            // 分页
            if (query.getPageSize() != null) {
                findIterable = findIterable.limit(query.getPageSize());
            }
        }

        List<Document> list = new ArrayList<>();
        if (findIterable != null) {
            for (Document document : findIterable) {
                document.put("_id", document.get("_id").toString());
                list.add(document);
            }
        } else {
            for (Document document : aggregateIterable) {
                document.put("_id", document.get("_id").toString());
                list.add(document);
            }
        }
        // 文档列表
        result.setDocuments(list);
        // 总共页数
        int page = (int) (documentCount / query.getPageSize());
        result.setTotalPage(documentCount % query.getPageSize() == 0 ? page : page + 1);
        // 每页大小
        result.setPageSize(query.getPageSize());
        // 文档总数
        result.setDocumentCount((int) documentCount);
        // 当前页数
        result.setCurrentPageNo(query.getCurrentPage());
        // 前一页
        result.setPrePage(Math.max(query.getCurrentPage() - 1, 1));
        // 下一页
        result.setNextPage(query.getCurrentPage() + 1 > result.getTotalPage() ? result.getTotalPage() : query.getCurrentPage() + 1);
        MongoUtils.closeClient(client);
        return Result.success(result);
    }

    @Override
    public Result getDocumentsInfo(String connectionId, String databaseName, String collectionName) {
        MongoClient client = MongoUtils.getClient(connectionId);
        MongoCollection<Document> collection = client.getDatabase(databaseName).getCollection(collectionName);
        Map<String, Object> result = new HashMap<>();
        result.put("indexes", StreamSupport.stream(collection.listIndexes().spliterator(), false).count());
        result.put("documents", collection.countDocuments());
        MongoUtils.closeClient(client);
        return Result.success(result);
    }

    @Override
    public Result insertDocument(List<Map<String, Object>> documents, String connectionId, String databaseName, String collectionName) {
        MongoClient client = MongoUtils.getClient(connectionId);
        MongoCollection<Document> collection = client.getDatabase(databaseName).getCollection(collectionName);
        String _id = "_id";
        if (documents.size() > 1) {
            // 插入多条
            List<Document> documentList = new ArrayList<>();
            documents.forEach(document -> {
                document.remove(_id);
                documentList.add(new Document(document));
            });
            try {
                collection.insertMany(documentList);
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return new Result("系统插入文档错误，请联系开发人员！", HttpStatus.ERROR);
            }
        } else {
            // 插入单条
            Map<String, Object> document = documents.get(0);
            Object id = document.get(_id);
            if (id != null) {
                // clone
                document.remove(_id);
            }
            try {
                collection.insertOne(new Document(document));
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return new Result("系统插入文档错误，请联系开发人员！", HttpStatus.ERROR);
            }
        }
        MongoUtils.closeClient(client);
        return Result.success();
    }

    @Override
    public Result dropDocumentsById(List<Map<String, Object>> documents, String connectionId, String databaseName, String collectionName) {
        MongoClient client = MongoUtils.getClient(connectionId);
        MongoCollection<Document> collection = client.getDatabase(databaseName).getCollection(collectionName);
        String _id = "_id";
        if (documents.size() > 1) {
            // 删除多个文档
            List<ObjectId> ids = new ArrayList<>();
            for (Map<String, Object> document : documents) {
                Object id = document.get(_id);
                if (id == null) {
                    return new Result("_id信息不能为空！", HttpStatus.BAD_REQUEST);
                }
                ids.add(new ObjectId(id.toString()));
            }
            try {
                if (collection.deleteMany(Filters.in(_id, ids)).getDeletedCount() < 1) {
                    MongoUtils.closeClient(client);
                    return Result.error("删除失败！");
                }
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return new Result("系统删除文档错误，请联系开发人员！", HttpStatus.ERROR);
            }
        } else {
            // 删除单个文档
            Map<String, Object> document = documents.get(0);
            Object id = document.get(_id);
            if (id == null) {
                return new Result("_id信息不能为空！", HttpStatus.BAD_REQUEST);
            }
            try {
                if (collection.deleteOne(Filters.eq(_id, new ObjectId(id.toString()))).getDeletedCount() < 1) {
                    MongoUtils.closeClient(client);
                    return Result.error("删除失败！");
                }
            } catch (Exception e) {
                e.printStackTrace();
                MongoUtils.closeClient(client);
                return new Result("系统删除文档错误，请联系开发人员！", HttpStatus.ERROR);
            }
        }
        MongoUtils.closeClient(client);
        return Result.success();
    }

    @Override
    public Result updateDocumentById(List<Map<String, Object>> documents, String connectionId, String databaseName, String collectionName) {
        // 单个文档更新
        Map<String, Object> document = documents.get(0);
        String _id = "_id";
        Object id = document.get(_id);
        if (id == null) {
            return new Result("_id信息不能为空！", HttpStatus.BAD_REQUEST);
        }
        MongoClient client = MongoUtils.getClient(connectionId);
        MongoCollection<Document> collection = client.getDatabase(databaseName).getCollection(collectionName);
        try {
            DeleteResult deleteResult = collection.deleteOne(Filters.eq(_id, new ObjectId(id.toString())));
            if (deleteResult.getDeletedCount() < 1) {
                MongoUtils.closeClient(client);
                return Result.error("更新文档失败！", HttpStatus.BAD_REQUEST);
            }
            document.remove("_id");
            document.put("_id", new ObjectId(id.toString()));
            InsertOneResult insertOneResult = collection.insertOne(new Document(document));
            if (insertOneResult.getInsertedId() == null) {
                MongoUtils.closeClient(client);
                return Result.error("更新文档失败！", HttpStatus.BAD_REQUEST);
            }
        } catch (Exception e) {
            e.printStackTrace();
            MongoUtils.closeClient(client);
            return new Result("系统更新文档错误，请联系开发人员！", HttpStatus.ERROR);
        }
        MongoUtils.closeClient(client);
        return Result.success();
    }

    /**
     * 构造过滤器
     *
     * @param filter 条件map
     * @return Bson
     */
    private Bson getFilter(Map<String, String> filter) {
        Bson bson = null;
        String key = filter.get("key");
        if (StringUtils.isBlank(key)) {
            throw new ServiceException("查询列不能为空！", HttpStatus.BAD_REQUEST);
        }
        Object value = filter.get("value");
        if (value == null || StringUtils.isBlank(value.toString())) {
            throw new ServiceException("查询值不能为空！", HttpStatus.BAD_REQUEST);
        }
        String type = filter.get("type");
        if (StringUtils.isBlank(type)) {
            throw new ServiceException("查询类型不能为空！", HttpStatus.BAD_REQUEST);
        }
        int typeCode = Integer.parseInt(type);
        List<Object> list = new ArrayList<>();
        switch (typeCode) {
            case 1:
                value = Integer.parseInt(value.toString());
                break;
            case 2:
                value = value.toString();
                break;
            case 3:
                value = Boolean.parseBoolean(value.toString());
                break;
            case 4:
                for (String str : StringUtils.split(value.toString(), ",")) {
                    if (StringUtils.isNumeric(str)) {
                        list.add(Integer.parseInt(str));
                    } else {
                        list.add(str);
                    }
                }
                break;
            default:
                break;
        }
        switch (Objects.requireNonNull(FilterConstant.fromFilterName(filter.get("filter")))) {
            case EQ:
                // ==
                if (typeCode == 4) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.eq(key, value);
                break;
            case NE:
                // !=
                if (typeCode == 4) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.ne(key, value);
                break;
            case GT:
                // >
                if (typeCode != 1) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.gt(key, value);
                break;
            case GTE:
                // >=
                if (typeCode != 1) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.gte(key, value);
                break;
            case LT:
                // <
                if (typeCode != 1) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.lt(key, value);
                break;
            case LTE:
                // <=
                if (typeCode != 1) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.lte(key, value);
                break;
            case IN:
                // in
                if (typeCode != 4) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.in(key, list);
                break;
            case NIN:
                // not in
                if (typeCode != 4) {
                    throw new ServiceException("类型不匹配！", HttpStatus.BAD_REQUEST);
                }
                bson = Filters.nin(key, list);
                break;
            default:
                break;
        }
        return bson;
    }

}
